/* eslint-disable no-restricted-syntax */
/* eslint-disable no-bitwise */
const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const ImageModule = require('docxtemplater-image-module-free');
const randomString = require('randomstring');
const mongoose = require('mongoose');

const files = require('./../../../files');
const BaseReporter = require('./../base-report');
const Docx = require('./../adapter/docx');
const tests = require('./../../../tests');
const { getCountableKinds } = require('../../../file-elements');
const functionsModel = require('./../../../functions');
const invokeRelations = require('./../../../invoke-relations');
const { versionType } = require('../../../project-versions');
const Function = require('../../../../models/mongo/function');
const Test = require('../../../../models/mongo/test-case');
const InvokeRelation = require('../../../../models/mongo/invoke-relation');
const File = require('../../../../models/mongo/file');
const classCreator = require('./../base-report-test-case-transformer');
const utility = require('../../../utility');
const CascoDrawFileInvokeFunc = require('./draw-file-invokefunc');
const DefaultReporter = require('../default-report');
const ProgressIndicator = require('../../../progress-indicator');
const split = true;

class CascoReporter extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null) {
    super(versionId, locale, version, project, creator, outputDir);
    this.images = new Map();
  }

  /**
   * 报告生成
   */
  generate(progressIndicator = new ProgressIndicator()) {
    const result = {
      cachePath: this.outputDir,
    };
    return new Promise((resolve, reject) => {
      this.spliteByFile(progressIndicator)
        .then((files) => {
          if (split) {
            return Promise.resolve();
          }
          files = files.filter((file) => file);
          return this.generateSummary(files);
        })
        .then(() => {
          const fileName = `${this.project.projectName}-${this.version.versionName}.${this.isIntegrationTest() ? '集成测试报告' : '单元测试报告'}.zip`;
          result.reportPath = path.join(this.outputDir, fileName);
          const reportsDir = path.join(this.outputDir, this.project.projectName);
          return compressing.zip.compressDir(reportsDir, result.reportPath);
        })
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  spliteByFile(progressIndicator = null) {
    return new Promise((resolve, reject) => {
      const result = {};
      this.spliteByProjectVersion((versionDir) => fs.ensureDir(versionDir))
        .then(({ projectDir, versionPath }) => {
          result.projectDir = projectDir;
          result.versionPath = versionPath;
          result.versionDir = versionPath;
          return files.getFiles(this.versionId, {}, { kinds: files.kind.file });
        })
        .then((resFiles) => resolve(this.handleFiles(resFiles, result.versionDir, null, progressIndicator)))
        .catch(reject);
    });
  }

  /**
   * 获取单个文件概要统计
   * @param {Array} functions 函数列表
   * @returns {Object}
   */
  static getSummaryOfFile(functions) {
    const summary = {
      functionCount: functions.filter(({ kind }) => getCountableKinds().includes(kind)).length,
      tests: {
        total: 0,
        succeed: 0,
        nonStrictSucceed: 0,
        failed: 0,
        noResult: 0,
        exception: 0,
        codeDefect: 0,
      },
    };
    functions.forEach((func) => {
      summary.tests.total += func.tests.length;
      func.tests.forEach((test) => {
        const key = BaseReporter.getTestResultKindKey(test.resultKind);
        test.resultKindName = key;
        if (summary.tests[key] === undefined) {
          return;
        }
        summary.tests[key] += 1;
      });
    });
    return summary;
  }

  /**
   * 生成测试报告总览
   * @returns {Promise}
   */
  async generateSummary(files) {
    let testsTotal = 0;
    const testTypeStatistic = {};
    let globalIdx = 0; // 全局编号
    for (const file of files) {
      for (const func of file.functions) {
        let inFuncIdx = 0; // 函数内编号
        for (const test of func.tests) {
          test.globalIndex = globalIdx + 1;
          test.index = `${inFuncIdx + 1}`.padStart(4, '0');
          globalIdx += 1;
          inFuncIdx += 1;
          testsTotal += 1;
          if (test.description) {
            if (testTypeStatistic[test.description] === undefined) {
              testTypeStatistic[test.description] = 0;
            }
            testTypeStatistic[test.description] += 1;
          }
        }
      }
    }
    const summary = {
      reportTime: moment().format('YYYY/MM/DD'),
      overview: {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
      },
      testsTotal,
      testTypeStatistic: Object.keys(testTypeStatistic).map((type) => ({ type, cnt: testTypeStatistic[type] })),
    };
    const reportPath = split
      ? path.join(
        this.outputDir,
        this.project.projectName,
        this.version.versionName,
        path.dirname(files[0].filePath),
        `${files[0].fileName}.docx`
      )
      : path.join(
        this.outputDir,
        this.project.projectName,
        this.version.versionName,
        'summary.docx'
      );
    let docx = new Docx(path.join(__dirname, 'casco.docx'));
    if (this.isIntegrationTest()) {
      docx = new Docx(path.join(__dirname, 'casco-it.docx'));
      docx.getAdapterInstance().attachModule(new ImageModule({
        centered: false,
        getImage: (reportPath) => fs.readFileSync(reportPath),
        getSize: (img, tagValue) => {
          if (!this.images.has(tagValue)) {
            return [0, 0];
          }
          const imageConfig = this.images.get(tagValue);
          if (!imageConfig) {
            throw new Error(`Can not get image size of ${imageConfig}`);
          }
          return [imageConfig.width, imageConfig.height];
        },
      }));
    }
    return fs.ensureDir(path.dirname(reportPath))
      .then(() => docx.generate({
        summary,
        files,
      }, reportPath, true));
  }

  /**
   * 获取某个函数的概要统计
   * @param {Object}} func 函数信息
   * @returns {Object}
   */
  getSummaryOfFunction(func) {
    let returnType = 'void';
    if (func.variables.output && func.variables.output['%']) {
      returnType = func.variables.output['%']['@type'];
    }
    let { functionNameArgs } = func;
    if (!functionNameArgs) {
      const params = [];
      if (func.variables.variables && func.variables.variables.params) {
        Object.keys(func.variables.variables.params).forEach((paramName) => {
          params.push(`${func.variables.variables.params[paramName]['@type']} ${paramName}`);
        });
      }
      functionNameArgs = `${func.functionName}(${params.join(', ')})`;
    }
    const summary = {
      declaration: `${returnType} ${functionNameArgs};`,
      testCount: func.tests.length,
      description: func.description,
    };
    const { coverages } = this.version;
    // eslint-disable-next-line no-bitwise
    if (func.statementTotal && (coverages & tests.coverageKind.statement) > 0) {
      summary.statementCoverage = func.statementCovered / func.statementTotal;
      summary.statementCoverage = Math.round(summary.statementCoverage * 10000) / 100;
    } else if ((coverages & tests.coverageKind.statement) > 0) {
      summary.statementCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.branchTotal && (coverages & tests.coverageKind.branch) > 0) {
      summary.branchCoverage = func.branchCovered / func.branchTotal;
      summary.branchCoverage = Math.round(summary.branchCoverage * 10000) / 100;
    } else if ((coverages & tests.coverageKind.branch) > 0) {
      summary.branchCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.mcdcTotal && (coverages & tests.coverageKind.mcdc) > 0) {
      summary.mcdcCoverage = func.mcdcCovered / func.mcdcTotal;
      summary.mcdcCoverage = Math.round(summary.mcdcCoverage * 10000) / 100;
    } else if ((coverages & tests.coverageKind.mcdc) > 0) {
      summary.mcdcCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.conditionTotal && (coverages & tests.coverageKind.condition) > 0) {
      summary.conditionCoverage = func.conditionCovered / func.conditionTotal;
      summary.conditionCoverage = Math.round(summary.conditionCoverage * 10000) / 100;
    } else if ((coverages & tests.coverageKind.condition) > 0) {
      summary.conditionCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.decisionTotal && (coverages & tests.coverageKind.decision) > 0) {
      summary.decisionCoverage = func.decisionCovered / func.decisionTotal;
      summary.decisionCoverage = Math.round(summary.decisionCoverage * 10000) / 100;
    } else if ((coverages & tests.coverageKind.decision) > 0) {
      summary.decisionCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.invokeTotal && (coverages & tests.coverageKind.invoke) > 0) {
      summary.invokeCoverage = func.invokeCovered / func.invokeTotal;
      summary.invokeCoverage = Math.round(summary.invokeCoverage * 10000) / 100;
    } else if ((coverages & tests.coverageKind.invoke) > 0) {
      summary.invokeCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.invokedFuncTotal && (coverages & tests.coverageKind.invokedFunc) > 0) {
      summary.invokedFuncCoverage = func.invokedFuncCovered / func.invokedFuncTotal;
      summary.invokedFuncCoverage = Math.round(summary.invokedFuncCoverage * 10000) / 100;
    } else if ((coverages & tests.coverageKind.invokedFunc) > 0) {
      summary.invokedFuncCoverage = '-';
    }
    const coverageArr = [];
    if (summary.invokedFuncCoverage) {
      coverageArr.push({
        name: this.i18n.__('tests.invokedFuncCoverageRate'),
        value: summary.invokedFuncCoverage,
      });
    }
    // 只显示函数覆盖率
    if (summary.invokeCoverage) {
      coverageArr.push({
        name: this.i18n.__('tests.invokeCoverageRate'),
        value: summary.invokeCoverage,
      });
    }
    // if (summary.statementCoverage) {
    //   coverageArr.push({
    //     name: this.i18n.__('tests.statementCoverageRate'),
    //     value: summary.statementCoverage,
    //   });
    // }
    // if (summary.branchCoverage) {
    //   coverageArr.push({
    //     name: this.i18n.__('tests.branchCoverageRate'),
    //     value: summary.branchCoverage,
    //   });
    // }
    // if (summary.conditionCoverage) {
    //   coverageArr.push({
    //     name: this.i18n.__('tests.conditionCoverageRate'),
    //     value: summary.conditionCoverage,
    //   });
    // }
    // if (summary.decisionCoverage) {
    //   coverageArr.push({
    //     name: this.i18n.__('tests.decisionCoverageRate'),
    //     value: summary.decisionCoverage,
    //   });
    // }
    // if (summary.mcdcCoverage) {
    //   coverageArr.push({
    //     name: this.i18n.__('tests.mcdcCoverageRate'),
    //     value: summary.mcdcCoverage,
    //   });
    // }
    summary.coverages = coverageArr;
    return summary;
  }

  /**
   * 获取用例列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Object} func 函数信息
   */
  getTestDetailsOfFunction(typeSystem, func) {
    const resTests = [];
    func.tests.sort((a, b) => {
      if (a.number < b.number) return -1; // eslint-disable-line no-underscore-dangle
      if (a.number > b.number) return 1; // eslint-disable-line no-underscore-dangle
      return 0;
    });
    const funcVariables = functionsModel.getFunctionVariableJson(func.variables);
    if (!this.isIntegrationTest()) {
      typeSystem = files.getTypesJson(func.fileId, typeSystem);
    }
    let caseIndex = 0;
    func.tests.forEach((test) => {
      const testCase = tests.getJson(test, this.i18n);
      const ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
      const transformer = new ReportTestCaseTransformer(
        testCase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      transformer.transform();

      const { coverages } = this.version;
      let statementCoverage; let branchCoverage; let mcdcCoverage;
      let conditionCoverage; let invokeCoverage; let invokedFuncCoverage;
      let decisionCoverage;
      if (func.statementTotal && (coverages & tests.coverageKind.statement) > 0) {
        statementCoverage = test.statementCovered / func.statementTotal;
        statementCoverage = `${Math.round(statementCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.statement) > 0) {
        statementCoverage = '-';
      }
      if (func.branchTotal && (coverages & tests.coverageKind.branch) > 0) {
        branchCoverage = test.branchCovered / func.branchTotal;
        branchCoverage = `${Math.round(branchCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.branch) > 0) {
        branchCoverage = '-';
      }
      if (func.mcdcTotal && (coverages & tests.coverageKind.mcdc) > 0) {
        mcdcCoverage = test.mcdcCovered / func.mcdcTotal;
        mcdcCoverage = `${Math.round(mcdcCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.mcdc) > 0) {
        mcdcCoverage = '-';
      }
      if (func.conditionTotal && (coverages & tests.coverageKind.condition) > 0) {
        conditionCoverage = test.conditionCovered / func.conditionTotal;
        conditionCoverage = `${Math.round(conditionCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.condition) > 0) {
        conditionCoverage = '-';
      }
      if (func.decisionTotal && (coverages & tests.coverageKind.decision) > 0) {
        decisionCoverage = test.decisionCovered / func.decisionTotal;
        decisionCoverage = `${Math.round(decisionCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.decision) > 0) {
        decisionCoverage = '-';
      }
      if (func.invokeTotal && (coverages & tests.coverageKind.invoke) > 0) {
        invokeCoverage = test.invokeCovered / func.invokeTotal;
        invokeCoverage = `${Math.round(invokeCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.invoke) > 0) {
        invokeCoverage = '-';
      }
      if (func.invokedFuncTotal && (coverages & tests.coverageKind.invokedFunc) > 0) {
        invokedFuncCoverage = test.invokedFuncCovered / func.invokedFuncTotal;
        invokedFuncCoverage = `${Math.round(invokedFuncCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.invokedFunc) > 0) {
        invokedFuncCoverage = '-';
      }

      const relations = [];
      const testMethod = [];
      if (this.isIntegrationTest()) {
        // function invoke relations
        relations.push({
          depth: 1,
          funcs: [{ name: func.functionName, stub: false }],
        });
        let level = 2;
        let levelFuncs = func.nextLevel.filter(
          (level2Func) => level2Func.coveredCaseIds && level2Func.coveredCaseIds.map((value) => String(value)).indexOf(String(testCase.testId)) > -1
        );
        while (levelFuncs.length && level < 4) {
          const nextLevelFuncs = [];
          const funcs = [];
          levelFuncs.forEach((currentFunc) => {
            funcs.push({
              name: currentFunc.functionName,
              stub: currentFunc.enableStub,
            });
            if (currentFunc.children) {
              nextLevelFuncs.push(...currentFunc.children.filter(
                (level3Func) => level3Func.coveredCaseIds
                  && level3Func.coveredCaseIds.map((value) => String(value)).indexOf(String(testCase.testId)) > -1
              ));
            }
          });
          relations.push({
            depth: level,
            funcs,
          });
          levelFuncs = nextLevelFuncs;
          level += 1;
        }
        // testMethod
        const codeToTestMethod = new Map(
          [
            [1, '等价类划分 Equivalence Partition'],
            [2, '边界值分析 Boundary Analysis'],
            [3, '错误猜测 Error Guess'],
            [4, '程序插桩 Program Insertion'],
            [5, '功能分解 Functional Decomposition'],
          ]
        );
        if (test.testMethod) {
          test.testMethod.forEach((code) => {
            testMethod.push(codeToTestMethod.get(code));
          });
        }
      }
      caseIndex += 1;
      resTests.push({
        identifier: test.identifier,
        description: testCase.description,
        requirementInfo: testCase.requirementInfo,
        resultKind: test.resultKindName,
        detail: {
          prelude: transformer.prelude,
          input: transformer.input,
          output: transformer.output,
          result: transformer.result,
        },
        statementCoverage,
        branchCoverage,
        mcdcCoverage,
        conditionCoverage,
        decisionCoverage,
        invokeCoverage,
        invokedFuncCoverage,
        caseIndex,
        invokeRelations: relations,
        testMethod: testMethod.join(','),
        testInvokeRelationsImage: test.testInvokeRelationsImage,
      });
    });
    return resTests;
  }

  async generateFileInvokeFuncImage(file) {
    const data = {
      files: {
        left: [],
        center: {
          id: file._id.toString(),
          name: file.fileName,
          functions: [],
        },
        right: [],
      },
      edges: [],
    };
    const funcs = await Function.find({ fileId: file._id }, ['_id', 'functionName']);
    await utility.arrayChunkOperation(funcs, 50, async (func) => {
      const functionId = func._id.toString();
      data.files.center.functions.push({
        id: functionId,
        name: func.functionName,
      });
      const invokeFuncs = await invokeRelations.retrieveRelations(functionId, { initRelations: true });
      invokeFuncs.forEach((invokeFunc) => {
        if (!invokeFunc.toFuncId) {
          return;
        }
        if (invokeFunc.fromFuncId.toString() === functionId) {
          const toFuncId = invokeFunc.toFuncId.toString();
          const toFuncFileId = invokeFunc.toFuncFileId.toString();
          const edge = {
            from: functionId,
            to: toFuncId,
          };
          if (toFuncFileId !== file._id.toString()) {
            edge.to = toFuncFileId;
            data.files.right.push(toFuncFileId);
          }
          data.edges.push(edge);
        }
      });
      const invokedFuncs = await InvokeRelation.find({ toFuncId: functionId, toFuncFileId: file._id, fromFuncFileId: { $ne: file._id } });
      invokedFuncs.forEach((invokedFunc) => {
        const fromFuncFileId = invokedFunc.fromFuncFileId.toString();
        data.files.left.push(fromFuncFileId);
        data.edges.push({
          from: fromFuncFileId,
          to: functionId,
        });
      });
    });
    await Promise.all(['left', 'right'].map(async (key) => {
      data.files[key] = (await File.find({ _id: { $in: data.files[key] } }, ['_id', 'fileName']))
        .map((item) => ({ id: item._id.toString(), name: item.fileName }));
    }));
    const cascoDrawFileInvokeFunc = new CascoDrawFileInvokeFunc(data, this.outputDir);
    const fileInvokeFuncImage = await cascoDrawFileInvokeFunc.generateImage();
    file.fileInvokeFuncImage = fileInvokeFuncImage.imagePath;
    this.images.set(fileInvokeFuncImage.imagePath, fileInvokeFuncImage);
  }

  handleFile(file, versionDir) {
    return new Promise((resolve, reject) => {
      super.handleFile(file, versionDir)
        .then(async (res) => {
          const { typeSystem, functions, resultCount } = res;
          if (!resultCount) {
            return null;
          }
          if (this.isIntegrationTest()) {
            const nextLevels = await Promise.all(
              functions.map((func) => functionsModel.getInvokeChildren(func._id, func._id, mongoose.Types.ObjectId(this.versionId), 2, false))
            );
            functions.forEach((func, index) => {
              func.nextLevel = nextLevels[index];
            });
            await utility.arrayChunkOperation(functions, 1,
              async (func) => {
                await this.generateFuncInvokeFuncImage(func, func.nextLevel);
                await utility.arrayChunkOperation(func.tests, 5, (testData) => this.generateFuncInvokeFuncImage(func, func.nextLevel, testData));
              });
          }
          return this.generateFileDocxReport(file, typeSystem, functions);
        })
        .then((data) => {
          if (split && data) {
            return resolve(this.generateSummary([data]));
          }
          return resolve(data);
        })
        .catch(reject);
    });
  }

  handleFiles(files, versionDir, callback = null, progressIndicator = null) {
    if (!split) {
      return utility.arrayChunkOperation(files, 1, async (file) => {
        const testCount = await this.getFileTestCount(file._id, { excuted: true });
        if (!testCount) {
          if (progressIndicator) {
            progressIndicator.advance(1 / files.length);
          }
          return null;
        }
        const data = await this.handleFile(file, versionDir);
        if (progressIndicator) {
          progressIndicator.advance(1 / files.length);
        }
        return data;
      });
    }
    return utility.arrayChunkOperation(files, 1, async (file) => {
      const testCount = await this.getFileTestCount(file._id, { excuted: true });
      if (!testCount) {
        if (progressIndicator) {
          progressIndicator.advance(1 / files.length);
        }
        return;
      }
      if (callback) {
        await callback(file, versionDir);
      }
      await this.handleFile(file, versionDir);
      if (progressIndicator) {
        progressIndicator.advance(1 / files.length);
      }
    });
  }

  async getFileTestCount(fileId, {
    excuted = undefined, // 是否执行
  }) {
    const opts = { fileId: mongoose.Types.ObjectId(fileId) };
    if (excuted === true) {
      opts.resultKind = { $nin: [tests.resultKind.unknown] };
    } else if (excuted === false) {
      opts.resultKind = { $in: [tests.resultKind.unknown] };
    }
    return Test.countDocuments(opts);
  }

  /**
   * 获取函数列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数列表
   * @returns {Array}
   */
  getFunctionDetailsOfFile(typeSystem, functions) {
    const resFunctions = [];
    functions.sort((a, b) => {
      if (a.index < b.index) return -1;
      if (a.index > b.index) return 1;
      return 0;
    });
    functions.forEach((func) => {
      if (!func.tests.length) {
        return;
      }
      resFunctions.push({
        functionName: func.functionName,
        description: func.description,
        summary: this.getSummaryOfFunction(func),
        tests: this.getTestDetailsOfFunction(typeSystem, func),
        integrationFunctions: func.integrationFunctions,
        integrationFunctionsCount: func.integrationFunctions.length,
        invokeRelationsImage: func.invokeRelationsImage,
      });
    });
    return resFunctions;
  }

  async generateFuncInvokeFuncImage(rootFunc, nextLevel, testData = undefined) {
    const funcId = rootFunc._id.toString();
    const gray = 'rgb(164, 177, 191)';
    const green = 'rgb(27, 173, 39)';
    const blue = 'rgb(59, 109, 242)';
    const fontblack = 'rgb(102, 102, 102)';
    const fontwhite = 'white';
    const graybg = 'rgb(219, 219, 219)';
    const whitebg = 'white';

    let leafCount = 0; // 记录叶子节点数量，用于估算图片尺寸height

    const tree = {
      functionId: funcId,
      functionName: rootFunc.functionName,
      children: [],
      // echarts数据 根节点
      label: {
        position: 'left',
        align: 'left',
        distance: 0,
        backgroundColor: blue,
        color: fontwhite,
      },
      name: rootFunc.functionName,
    };
    const children = [];
    nextLevel.forEach((func) => {
      const node = {};
      node.covered = testData ? !!(func.coveredCaseIds
        && func.coveredCaseIds.map((value) => String(value)).indexOf(testData._id.toString()) > -1)
        : !!(func.coveredCaseIds && func.coveredCaseIds.length);
      node.itemStyle = {
        borderWidth: 0,
        color: node.covered ? green : gray,
      };
      node.lineStyle = {
        borderWidth: 0,
        color: node.covered ? green : gray,
      };
      // 函数指针
      if (func.functionId) {
        node.name = `⊱ ${func.functionName}`;
      } else if (func.functionType === 'funcPointer') {
        node.name = `> ${func.functionName}`;
      } else {
        node.name = `? ${func.functionName}`;
      }
      node.label = {
        // eslint-disable-next-line no-nested-ternary
        backgroundColor: node.covered ? green : (func.enableStub ? graybg : whitebg),
        color: node.covered ? fontwhite : fontblack,
        borderColor: 'rgb(164, 177, 190)',
        // eslint-disable-next-line no-nested-ternary
        borderWidth: node.covered ? 0 : (func.enableStub ? 0 : 1),
      };
      children.push(node);
    });
    tree.children = children;
    leafCount = children.length;
    const options = {
      series: [
        {
          type: 'tree',
          data: [tree],
          top: '1%',
          left: '5%',
          bottom: '1%',
          right: '50%',
          symbolSize: 20,
          symbol: 'triangle',
          symbolRotate: -90,
          orient: 'LR',
          label: {
            fontSize: 40,
            position: 'right',
            distance: 0,
            verticalAlign: 'middle',
            align: 'left',
            backgroundColor: green,
            borderRadius: 5,
            padding: 20,
          },
        },
      ],
    };

    // 估算图片尺寸
    const minHeight = 200;
    const height = leafCount * 25 > minHeight ? leafCount * 25 : minHeight;
    const width = 500;

    const outputPath = path.join(this.outputDir, `${randomString.generate(16)}.png`);
    await utility.writeFile(DefaultReporter.getEChartsImage(options, { width, height }), outputPath);
    this.images.set(outputPath, {
      width, height,
    });
    if (testData) {
      testData.testInvokeRelationsImage = outputPath;
    } else {
      rootFunc.invokeRelationsImage = outputPath;
    }
  }

  generateFileDocxReport(file, typeSystem, functions) {
    return {
      fileName: file.fileName,
      filePath: file.path,
      fileShortName: file.fileName.substring(0, file.fileName.lastIndexOf('.')),
      summary: CascoReporter.getSummaryOfFile(functions),
      fileInvokeFuncImage: file.fileInvokeFuncImage,
      functions: this.getFunctionDetailsOfFile(typeSystem, functions),
    };
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest || vType === versionType.integrationTest;
  }
}

/**
 * Export model definition object.
 */
module.exports = CascoReporter;
